package cn.asiontang.db;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import cn.asiontang.exception.LogEx;

/**
 * 此Dao可以被子类重载！也可以直接作为Helper类直接使用。<br/>
 * DAO子类千万不能使用匿名类！如：
 *
 * <pre>
 * new BaseEntityDao(context);
 *
 * <pre/>
 * 否则无法正常获取T.class！出现 Cast .. ParameterizedType Exception !
 *
 * @author AsionTang
 * @since 2013年9月24日
 */
public abstract class BaseEntityDao<T extends BaseEntity>
{
    private static final String TAG = "BaseEntityDao";
    protected final Class<T> tClass;
    protected final Context mContext;

    @SuppressWarnings("unchecked")
    public BaseEntityDao(final Context context)
    {
        this.mContext = context;

        //混淆时，此处会包错：java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType - 张波的技术积累 - 博客频道 - CSDN.NET
        //http://blog.csdn.net/woshisap/article/details/7711412
        Type genType = this.getClass().getGenericSuperclass();
        if (genType instanceof ParameterizedType)
            this.tClass = (Class<T>) ((ParameterizedType) genType).getActualTypeArguments()[0];
        else
            this.tClass = (Class<T>) genType;
    }

    public Context getContext()
    {
        return this.mContext;
    }

    /**
     * {@link #getList(int, Object...)}
     */
    protected List<T> getList(final Cursor cursor)
    {
        final List<T> list = new ArrayList<T>();
        try
        {
            final int count = cursor.getCount();
            if (count == 0)
                return list;

            final String[] columnNames = cursor.getColumnNames();
            ContentValues item;
            while (cursor.moveToNext())
            {
                item = new ContentValues();
                for (int i = 0; i < columnNames.length; i++)
                    item.put(columnNames[i], cursor.getString(i));

                final T t = this.tClass.newInstance();
                t.setValues(item);
                list.add(t);
            }
            return list;
        }
        catch (final InstantiationException e)
        {
            LogEx.e(TAG, e);
        }
        catch (final IllegalAccessException e)
        {
            LogEx.e(TAG, e);
        }
        finally
        {
            DBHelper.closeSilently(cursor);
        }
        return list;
    }

    /**
     * @param tClass   BaseEntity的子类的Class
     * @param sqlResId 直接传递 String Resource 中 的SQL语句的ID
     * @param args     判断SQL语句中是否有 String.format 的 格式化 占位符<br/>
     *                 【有】，则直接带入参数。省却了手动写String.format 的麻烦。<br/>
     *                 【没有】时，忽略此参数即可。即只需3参数 getList(tClass, mContext, sqlResId）即可。
     */
    protected List<T> getList(final int sqlResId, final Object... args)
    {
        if (args.length == 0)
            return this.getList(DBHelper.getCursor(sqlResId));
        else
            return this.getList(DBHelper.getCursor(sqlResId, args));
    }

    protected List<T> getList(final String sql, final Object... args)
    {
        if (args.length == 0)
            return this.getList(DBHelper.getCursor(sql));
        else
            return this.getList(DBHelper.getCursor(sql, args));
    }

    /**
     * 查询时，使用参数化传递Value，防止SQL注入。
     */
    protected List<T> getListByArgs(final int sqlResId, final String... args)
    {
        if (args.length == 0)
            return this.getList(DBHelper.getCursor(sqlResId));
        else
            return this.getList(DBHelper.getCursorByArgs(sqlResId, args));
    }

    protected List<T> getListByArgs(final String sql, final String... args)
    {
        if (args.length == 0)
            return this.getList(DBHelper.getCursor(sql));
        else
            return this.getList(DBHelper.getCursorByArgs(sql, args));
    }

    /**
     * 从数据库直接取出以指定列为Key，当前Entity为Value 的一个Map
     * <p>
     * <b>所以必须确保，作为KEY的一列必须唯一，不能重复！</b>
     * </p>
     */
    protected Map<String, T> getMap(final String keyColumnName, final Cursor cursor)
    {
        final Map<String, T> map = new HashMap<String, T>();
        try
        {
            final int count = cursor.getCount();
            if (count == 0)
                return map;

            final String[] columnNames = cursor.getColumnNames();
            while (cursor.moveToNext())
            {
                String keyValue = null;
                final ContentValues item = new ContentValues();
                for (int i = 0; i < columnNames.length; i++)
                {
                    if (keyColumnName.equals(columnNames[i]))
                        keyValue = cursor.getString(i);
                    item.put(columnNames[i], cursor.getString(i));
                }

                final T t = this.tClass.newInstance();
                t.setValues(item);
                map.put(keyValue, t);
            }
        }
        catch (final InstantiationException e)
        {
            LogEx.e(TAG, e);
        }
        catch (final IllegalAccessException e)
        {
            LogEx.e(TAG, e);
        }
        finally
        {
            DBHelper.closeSilently(cursor);
        }
        return map;
    }

    /**
     * {@link #getMap(String, String)}
     */
    protected Map<String, T> getMap(final String keyColumnName, final int sqlResId, final Object... args)
    {
        if (args.length == 0)
            return this.getMap(keyColumnName, DBHelper.getCursor(sqlResId));
        else
            return this.getMap(keyColumnName, DBHelper.getCursor(sqlResId, args));
    }

    protected Map<String, T> getMap(final String keyColumnName, final String sql, final Object... args)
    {
        if (args.length == 0)
            return this.getMap(keyColumnName, DBHelper.getCursor(sql));
        else
            return this.getMap(keyColumnName, DBHelper.getCursor(sql, args));
    }

    protected Map<String, T> getMapByArgs(final String keyColumnName, final int sqlResId, final String... args)
    {
        if (args.length == 0)
            return this.getMap(keyColumnName, DBHelper.getCursor(sqlResId));
        else
            return this.getMap(keyColumnName, DBHelper.getCursorByArgs(sqlResId, args));
    }

    protected Map<String, T> getMapByArgs(final String keyColumnName, final String sql, final String... args)
    {
        if (args.length == 0)
            return this.getMap(keyColumnName, DBHelper.getCursor(sql));
        else
            return this.getMap(keyColumnName, DBHelper.getCursorByArgs(sql, args));
    }

    /**
     * 批量保存 T 实体到数据库
     *
     * @param entityList T 实体列表
     */
    protected String save(final String tableName, final List<T> entityList)
    {
        DBHelper.beginTransaction();
        try
        {
            for (final T entity : entityList)
                DBHelper.insertOrReplace(tableName, entity.getValues());

            DBHelper.setTransactionSuccessful();
            return null;
        }
        catch (final Exception e)
        {
            LogEx.e(TAG, tableName, e);
            return e.getMessage();
        }
        finally
        {
            DBHelper.endTransaction();
        }
    }

    /**
     * 【必须】只能在entity对象仅仅包含一张表的字段前提下，正常save<br/>
     * 使用 SQLiteDatabase.CONFLICT_REPLACE 插入保存到数据库！ 所以为了保存，务必要具有表里面ID字段值
     */
    protected String save(final String tableName, final T entity)
    {
        DBHelper.beginTransaction();
        try
        {
            DBHelper.insertOrReplace(tableName, entity.getValues());
            DBHelper.setTransactionSuccessful();
            return null;
        }
        catch (final Exception e)
        {
            LogEx.e(TAG, tableName, e);
            return e.getMessage();
        }
        finally
        {
            DBHelper.endTransaction();
        }
    }
}